package com.example.videocompressors;

import ohos.app.Context;
import ohos.media.codec.Codec;
import ohos.media.codec.CodecDescription;
import ohos.media.codec.CodecDescriptionList;
import ohos.media.common.BufferInfo;
import ohos.media.common.Format;
import ohos.media.common.Source;
import ohos.media.extractor.Extractor;
import ohos.media.photokit.metadata.AVMetadataHelper;
import ohos.utils.net.Uri;

import java.io.*;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

//@SuppressLint("NewApi")
public class MediaController {
    public static File cachedFile;
    public String path;
    public static Context mContext;

    public final static String MIME_TYPE = "video/avc";
    private final static int PROCESSOR_TYPE_OTHER = 0;
    private final static int PROCESSOR_TYPE_QCOM = 1;
    private final static int PROCESSOR_TYPE_INTEL = 2;
    private final static int PROCESSOR_TYPE_MTK = 3;
    private final static int PROCESSOR_TYPE_SEC = 4;
    private final static int PROCESSOR_TYPE_TI = 5;
    private static volatile MediaController Instance = null;
    private boolean isVideoConvertFirstWrite = true;
    private final static int DEFAULT_VIDEO_WIDTH = 640;
    private final static int DEFAULT_VIDEO_HEIGHT = 360;
    private final static int DEFAULT_VIDEO_BITRATE = 450000;
    private int mWriteTimes = 0;
    Codec decoder = null;
    Codec encoder = null;
    long startTime = -1;
    long endTime = -1;
    int rotateRender = 0;
    long videoStartTime = 0;
    long time = 0;
    int swapUV = 0;
    int videoTrackIndex = -5;
    int bufferSize = 0;
    ByteBuffer[] decoderInputBuffers = null;
    ByteBuffer[] encoderOutputBuffers = null;
    ByteBuffer[] encoderInputBuffers = null;

    public static MediaController getInstance(Context context) {
        MediaController localInstance = Instance;
        mContext = context;
        if (localInstance == null) {
            synchronized (MediaController.class) {
                localInstance = Instance;
                if (localInstance == null) {
                    Instance = localInstance = new MediaController();
                }
            }
        }
        return localInstance;
    }

    public boolean convertVideo(final String sourcePath, File destDir) {
        return convertVideo(sourcePath, destDir, 0, 0, 0);
    }

    public boolean convertVideo(Context context, Uri videoContentUri, File destDir) {
        return convertVideo(context, videoContentUri, destDir, 0, 0, 0);
    }

    public boolean convertVideo(final String sourcePath, File destDir, int outWidth, int outHeight, int outBitrate) {
        return convertVideos(sourcePath, destDir, outWidth, outHeight, outBitrate);
    }

    public boolean convertVideo(Context context, Uri videoContentUri, File destDir, int outWidth, int outHeight, int outBitrate) {
        return convertVideo(context, videoContentUri, destDir, outWidth, outHeight, outBitrate);
    }

    private Codec.ICodecListener decodecListener = new Codec.ICodecListener() {
        @Override
        public void onReadBuffer(ByteBuffer byteBuffer, BufferInfo info, int outputBufferIndex) {
            boolean isDoRender = true;
            boolean isDrop = false;
            if (endTime > 0 && info.timeStamp >= endTime) {
                isDoRender = false;
                info.bufferType |= BufferInfo.BUFFER_TYPE_END_OF_STREAM;
                decoder.stop();
                decoder.release();
                endTag();
                return;
            }
            if (info.timeStamp < startTime) {
                isDoRender = false;
                isDrop = true;
            }
            if (info.bufferType == BufferInfo.BUFFER_TYPE_END_OF_STREAM) {
                decoder.stop();
                decoder.release();
                endTag();
                return;
            }
        }

        @Override
        public void onError(int i, int i1, int i2) {
        }
    };

    private void endTag() {
    }

    private Codec.ICodecListener encodecListener = new Codec.ICodecListener() {
        @Override
        public void onReadBuffer(ByteBuffer outputBuffer, BufferInfo info, int outputBufferIndex) {
            if (outputBuffer == null) {
                return;
            } else {
                if (outputBufferIndex == -1) {
                    return;
                } else {
                    if (info.bufferType == BufferInfo.BUFFER_TYPE_END_OF_STREAM && info.timeStamp < 0) {
                        info.timeStamp = 0;
                    }
                }
            }
        }

        @Override
        public void onError(int i, int i1, int i2) {
        }
    };

    private boolean convertVideos(String sourcePath, File destDir, int outWidth, int outHeight, int outBitrate) {
        AVMetadataHelper retriever = new AVMetadataHelper();

//        if (sourcePath != null) {

//            ResourceManager resourceManager = this.mContext.getResourceManager();
//            RawFileEntry rawFileEntry = resourceManager.getRawFileEntry(sourcePath);
//            RawFileDescriptor rawFileDescriptor_source = null;
//            try {
//                rawFileDescriptor_source = rawFileEntry.openRawFileDescriptor();
//            } catch (IOException e) {
//                e.printStackTrace();
//            }

//        } else {
//            return false;
//        }
//        /storage/emulated/0/Android/data/com.vincent.videocompressor/cache
        this.path = sourcePath;
        File file1 = new File(sourcePath + "/bbb.mp4");
        FileInputStream in1 = null;
        try {
            in1 = new FileInputStream(file1);
            FileDescriptor fd1 = in1.getFD();
            retriever.setSource(fd1);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String width = retriever.resolveMetadata(retriever.AV_KEY_VIDEO_HEIGHT);
        String height = retriever.resolveMetadata(retriever.AV_KEY_VIDEO_WIDTH);
        String rotation = retriever.resolveMetadata(retriever.AV_KEY_VIDEO_ROTATION);
        long duration = Long.valueOf(retriever.resolveMetadata(retriever.AV_KEY_DURATION)) * 1000;
        startTime = -1;
        endTime = -1;
        int rotationValue = Integer.valueOf(rotation);
        ;
        int originalWidth = Integer.valueOf(width);
        int originalHeight = Integer.valueOf(height);
        int bitrate = outBitrate > 0 ? outBitrate : DEFAULT_VIDEO_BITRATE;

        int resultWidth = 0;
        int resultHeight = 0;
        if (resultWidth % 2 != 0) {
            resultWidth--;
        }
        if (resultHeight % 2 != 0) {
            resultHeight--;
        }
        resultWidth = originalWidth * 2 / 3;
        resultHeight = originalHeight * 2 / 3;
        bitrate = resultWidth * resultHeight * 30;
//        if (rotation != null) {
//            rotationValue = Integer.valueOf(rotation);
//        }
//        if (width != null) {
//            originalWidth = Integer.valueOf(width);
//        }
//        if (height != null) {
//            originalHeight = Integer.valueOf(height);
//        }

        rotateRender = 0;

        File cacheFile = null;
        try {
            cacheFile = new File(getCompressedVideoPath(destDir));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        if (resultHeight > resultWidth && resultWidth != originalWidth && resultHeight != originalHeight) {
            int temp = resultHeight;
            resultHeight = resultWidth;
            resultWidth = temp;
            rotationValue = 90;
            rotateRender = 270;
        } else {
            if (rotationValue == 90) {
                int temp = resultHeight;
                resultHeight = resultWidth;
                resultWidth = temp;
                rotationValue = 0;
                rotateRender = 270;
            } else if (rotationValue == 180) {
                rotateRender = 180;
                rotationValue = 0;
            } else if (rotationValue == 270) {
                int temp = resultHeight;
                resultHeight = resultWidth;
                resultWidth = temp;
                rotationValue = 0;
                rotateRender = 90;
            }
        }
//        FileDescriptor fileDescriptor = null;
//        try {
//            BaseFileDescriptor descriptor = null;
//
//            if (sourcePath != null) {
//                descriptor = this.mContext.getResourceManager().getRawFileEntry(sourcePath+"/bbb.mp4").openRawFileDescriptor();
//            } else {
//                DataAbilityHelper helper = DataAbilityHelper.creator(mContext);
//                descriptor = helper.openRawFile(Uri.parse(sourcePath+"/bbb.mp4"), "r");
//            }
//            fileDescriptor = descriptor.getFileDescriptor();
//        } catch (FileNotFoundException e) {
//            e.printStackTrace();
//            return false;
//        } catch (IOException e) {
//            e.printStackTrace();
//        } catch (DataAbilityRemoteException e) {
//            e.printStackTrace();
//        }
        isVideoConvertFirstWrite = true;
        boolean isError = false;
        videoStartTime = startTime;

        time = System.currentTimeMillis();
        if (resultWidth != 0 && resultHeight != 0) {
            MP4Builder mediaMuxer = null;
            Extractor extractor = null;

            try {
                BufferInfo info = new BufferInfo();
                Mp4Movie movie = new Mp4Movie();
                movie.setCacheFile(cacheFile);
                movie.setRotation(rotationValue);
                movie.setSize(resultWidth, resultHeight);
                mediaMuxer = new MP4Builder().createMovie(movie);
                extractor = new Extractor();
                Source source = null;
                if (sourcePath != null) {
                    File file = new File(sourcePath + "/bbb.mp4");
                    FileInputStream in = new FileInputStream(file);
                    FileDescriptor fd = in.getFD();
                    source = new Source(fd);
                    extractor.setSource(source);
                }
//                if (context != null && videoContentUri != null) {
//                    source = new Source(String.valueOf(videoContentUri));
//                    extractor.setSource(source);
//                }
                else {
                    return false;
                }
                int videoIndex;
                videoIndex = selectTrack(extractor, false);

                if (videoIndex >= 0) {
                    decoder = null;
                    encoder = null;
                    InputSurface inputSurface = null;
                    OutputSurface outputSurface = null;
                    try {
                        long videoTime = -1;
                        boolean isInputDone = false;
                        swapUV = 0;
                        videoTrackIndex = -5;
                        int colorFormat;
                        int processorType = PROCESSOR_TYPE_OTHER;
                        colorFormat = 0x7F000789;
                        int resultHeightAligned = resultHeight;
                        int padding = 0;
                        bufferSize = resultWidth * resultHeight * 3 / 2;
                        if (processorType == PROCESSOR_TYPE_OTHER) {
                            if (resultHeight % 16 != 0) {
                                resultHeightAligned += (16 - (resultHeight % 16));
                                padding = resultWidth * (resultHeightAligned - resultHeight);
                                bufferSize += padding * 5 / 4;
                            }
                        } else if (processorType == PROCESSOR_TYPE_QCOM) {
                            int uvoffset = (resultWidth * resultHeight + 2047) & ~2047;
                            padding = uvoffset - (resultWidth * resultHeight);
                            bufferSize += padding;
                        } else if (processorType == PROCESSOR_TYPE_MTK) {
                            resultHeightAligned += (16 - (resultHeight % 16));
                            padding = resultWidth * (resultHeightAligned - resultHeight);
                            bufferSize += padding * 5 / 4;
                        }

                        extractor.specifyStream(videoIndex);
                        if (startTime > 0) {
                            extractor.rewindTo(startTime, Extractor.REWIND_TO_PREVIOUS_SYNC);
                        } else {
                            extractor.rewindTo(0, Extractor.REWIND_TO_PREVIOUS_SYNC);
                        }
                        Format inputFormat = extractor.getStreamFormat(videoIndex);
                        Format outputFormat = new Format();
                        decoder = Codec.createDecoder();
                        outputFormat.putStringValue(Format.MIME, Format.VIDEO_AVC);
                        outputFormat.putIntValue(Format.WIDTH, resultWidth);
                        outputFormat.putIntValue(Format.HEIGHT, resultHeight);
                        outputFormat.putIntValue(Format.COLOR_MODEL, colorFormat);
                        outputFormat.putIntValue(Format.BIT_RATE, bitrate);
                        outputFormat.putIntValue(Format.FRAME_RATE, 25);
                        outputFormat.putIntValue(Format.FRAME_INTERVAL, 10);
                        decoder.setCodecFormat(outputFormat);
                        encoder = Codec.createEncoder();
                        outputFormat.putStringValue(Format.MIME, MIME_TYPE);
                        encoder.setCodecFormat(outputFormat);
                        Codec finalEncoder = encoder;
                        encoder.registerCodecListener(new Codec.ICodecListener() {
                            @Override
                            public void onReadBuffer(ByteBuffer byteBuffer, BufferInfo bufferInfo, int i) {
                                Format fmt = finalEncoder.getBufferFormat(byteBuffer);
                            }

                            @Override
                            public void onError(int i, int i1, int i2) {
                            }
                        });
                        inputSurface = new InputSurface(encoder.obtainInputSurface());
                        inputSurface.makeCurrent();
                        encoder.start();
                        inputFormat.putStringValue(Format.MIME, inputFormat.getStringValue(Format.MIME));
                        decoder.setCodecFormat(inputFormat);
                        outputSurface = new OutputSurface();
                        Codec finalDecoder = decoder;
                        decoder.registerCodecListener(decodecListener);
                        decoder.start();
                        final int TIMEOUT_USEC = 2500;
                        decoderInputBuffers = null;
                        encoderOutputBuffers = null;
                        encoderInputBuffers = null;
                        ByteBuffer byteBuffer = decoder.getAvailableBuffer(TIMEOUT_USEC);
                        decoder.writeBuffer(byteBuffer, info);
                        while (!isInputDone) {
                            boolean isEof = false;
                            int id = extractor.getStreamId();
                            isEof = (id == -1);
                            if (!isEof) {
                                ByteBuffer inputBuf = encoder.getAvailableBuffer(TIMEOUT_USEC);
                                encoder.writeBuffer(inputBuf, info);
                                if (inputBuf == null) {
                                    try {
                                        Thread.sleep(10);
                                    } catch (InterruptedException e) {
                                        e.printStackTrace();
                                    }
                                    continue;
                                }
                                int chunkSize = extractor.readBuffer(inputBuf, 0);
                                long sampleTime = extractor.getFrameTimestamp();
                                int bufferType = extractor.getFrameType();

                                info = new BufferInfo();
                                info.offset = 0;
                                info.size = chunkSize;
                                info.timeStamp = sampleTime;
                                info.bufferType = bufferType;
//                                decoder.writeBuffer(inputBuf, info);
                                extractor.next();
                                mWriteTimes++;
                            } else {
                                ByteBuffer inputBuf = decoder.getAvailableBuffer(TIMEOUT_USEC);
                                int chunkSize = extractor.readBuffer(inputBuf, 0);
                                long sampleTime = extractor.getFrameTimestamp();
                                info = new BufferInfo();
                                info.offset = 0;
                                info.size = chunkSize;
                                info.timeStamp = sampleTime;
                                info.bufferType = BufferInfo.BUFFER_TYPE_END_OF_STREAM;
//                                decoder.writeBuffer(inputBuf, info);
                                mWriteTimes++;
                                extractor.next();
                                isInputDone = true;
                                continue;
                            }
                        }

                        if (videoTime != -1) {
                            videoStartTime = videoTime;
                        }
                    } catch (Exception e) {
                        isError = true;
                    }

                    extractor.unspecifyStream(videoIndex);

                    if (outputSurface != null) {
                        outputSurface.release();
                    }
                    if (inputSurface != null) {
                        inputSurface.release();
                    }
                    if (decoder != null) {
                        decoder.stop();
                        decoder.release();
                    }
                    if (encoder != null) {
                        encoder.stop();
                        encoder.release();
                    }
                }

                if (!isError) {
                }
            } catch (Exception e) {
                isError = true;
            } finally {
                if (extractor != null) {
                    extractor.release();
                }
                if (mediaMuxer != null) {
                    try {
                        mediaMuxer.finishMovie(false);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        } else {
            return false;
        }
        cachedFile = cacheFile;
        return true;
    }

    public static CodecDescription selectCodec(String mimeType) {
        CodecDescriptionList codecDescriptionList = new CodecDescriptionList();
        List<CodecDescription> supportedCodecs = codecDescriptionList.getSupportedCodecs();
        CodecDescription lastCodecInfo = null;
        for (int i1 = 0; i1 < supportedCodecs.size(); i1++) {
            CodecDescription codecInfo = supportedCodecs.get(i1);
            if (!codecInfo.isEncoder()) {
                continue;
            }
            String[] types = codecInfo.getMimeTypes();
            for (String type : types) {
                if (type.equalsIgnoreCase(mimeType)) {
                    lastCodecInfo = codecInfo;
                    if (!lastCodecInfo.getName().equals("OMX.SEC.avc.enc")) {
                        return lastCodecInfo;
                    } else if (lastCodecInfo.getName().equals("OMX.SEC.AVC.Encoder")) {
                        return lastCodecInfo;
                    }
                }
            }
        }
        return lastCodecInfo;
    }

    public String getCompressedVideoPath(File destDirectory) throws FileNotFoundException {
        String extension = ".mp4";
        if (!destDirectory.exists()) {
            destDirectory.mkdirs();
        }
        return (destDirectory.getAbsolutePath() + "/VIDEO_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + extension);
    }

    private int selectTrack(Extractor extractor, boolean isAudio) {
        int numTracks = extractor.getTotalStreams();
        for (int i1 = 0; i1 < numTracks; i1++) {
            Format format = extractor.getStreamFormat(i1);
            String mime = format.getStringValue(Format.MIME);
            if (isAudio) {
                if (mime.startsWith("audio/")) {
                    return i1;
                }
            } else {
                if (mime.startsWith("video/")) {
                    return i1;
                }
            }
        }
        return -5;
    }
}